/*
 * Copyright 2022 The Open Islands Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.openislands.oi.util;

import org.openislands.oi.pojo.base.BeanBuilder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;

import java.lang.reflect.*;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class ValueCopyUtils {
    /***
     * Copies the object source field value with the same name as the target to the target
     * @param source        source
     * @param targetClass   target class
     * @param <S>           source type
     * @param <T>           target type
     * @return target
     */
    public static <S, T> T copyProperties(S source, Class<T> targetClass) {
        T target = ReflectUtils.instantiateClass(targetClass);
        return copyProperties(source, target);
    }

    /***
     * Copies the object source field value with the same name as the target to the target
     * @param target    target
     */
    public static <S, T> T copyProperties(S source, T target) {
        try {
            BeanUtils.copyProperties(source, target);
        } catch (BeansException e) {
            return null;
        }
        return target;
    }

    /***
     * @param function  special handling copy function
     */
    public static <S, T> T copyProperties(S source, T target, ValueCopyFunction<S, T> function) {
        return function.copy(source, copyProperties(source, target));
    }

    /***
     * Convert collection element type
     * @param source    source collection
     * @param function  convert function
     * @param <T>       source type
     * @param <R>       result type
     * @return List<R>
     */
    public static <T, R> List<R> copyList(Collection<T> source, Function<T, R> function) {
        return source.stream().map(function).collect(Collectors.toList());
    }

    public static <T, R> List<R> copyList(Collection<T> source, Class<R> resultClass) {
        return source.stream().map(s -> copyProperties(s, resultClass)).collect(Collectors.toList());
    }

    public static <T, R> List<R> copyBuilderList(Collection<T> source, Function<BeanBuilder<T>, R> function) {
        return source.stream().map(t -> function.apply(BeanBuilder.create(t))).collect(Collectors.toList());
    }

    /***
     * 通过反射设置上个方法栈的参数, 并将params映射set到当前source同名字段
     * @param source    需要set参数的对象
     * @param params    参数列表, 类型与数量顺序需要与调用方法保持一致
     * @param <R>       对象泛型
     * @return source
     */
    public static <R> R methodParamsMapping(R source, Object... params) {
        final StackTraceElement[] traceElements = Thread.currentThread().getStackTrace();
        // [getStackTrace, methodParamsMapping, 调用方法]
        final StackTraceElement traceElement = traceElements[2];
        try {
            // 获取调用类
            final String className = traceElement.getClassName();
            Class<?> clazz = Class.forName(className);
            // 获取调用方法
            Method method = ReflectUtils.findAnyClassMethod(clazz, traceElement.getMethodName(), params);
            // 获取方法入参
            final Parameter[] parameters = method.getParameters();
            // 反射设置同名字段值
            final Class<?> aClass = source.getClass();
            Map<String, Field> fieldMap = new HashMap<>();
            Field[][] fields2 = new Field[][]{aClass.getFields(), aClass.getDeclaredFields()};
            Arrays.stream(fields2).forEach(fields -> Arrays.stream(fields).forEach(f -> fieldMap.put(f.getName(), f)));
            for (int i = 0; i < parameters.length && i < params.length; i++) {
                final Parameter parameter = parameters[i];
                final String name = parameter.getName();
                try {
                    // 获取字段赋值
                    Field field = fieldMap.get(name);
                    // 判断类型是否一致
                    if (field != null && parameter.getType() == field.getType()) {
                        // 设置private权限
                        field.setAccessible(true);
                        // 设置值
                        field.set(source, params[i]);
                    }
                } catch (Exception e) {
                    // 没有同名字段
                }
            }
        } catch (Exception ignored) {
        }

        return source;
    }

    @FunctionalInterface
    public interface ValueCopyFunction<S, T> {
        T copy(S source, T target);
    }
}
